/*
Pyxis 2.0
Copyright 2010-2011 Thomas W. Holtquist

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Threading;

using Microsoft.SPOT;

using GHIElectronics.NETMF.Hardware;

using Skewworks.Pyxis.EXT;
using Skewworks.Pyxis.GUI.Controls;

namespace Skewworks.Pyxis.Kernel
{

    class FileFinder
    {

        #region Variables

        private static PyxisAPI API;
        private static bool bLoaded = false;
        private static Bitmap buffer2 = null;
        private static MenuItem mnuQuitApp;
        private static MenuItem mnuSwitchToApp;

        private static Form frmFinder;
        private static Filebox fbox;
        private static Treeview trvFolders;
        private static Combobox cboDrives;
        private static MenuItem mnu;

        private static ArrayList _persistance;
        private static string _selFolder;
        private static string _selFile;

        private static ContextMenu CM;
        private static ModalMessage MM;

        #endregion
        
        #region Public Methods

        public static void Show(PyxisAPI api)
        {

            API = api;
            if (API.desktop._mnu.count > 1) 
                API.desktop._mnu.remove(1);

            if (!bLoaded)
            {
                MM = new ModalMessage("Loading File Finder; Please Wait", api);
                new Thread(CreateApp).Start();
                MM.Start();
                bLoaded = true;
            }

            // Display App
            API.desktop._mnu.add(mnu);
            API.ActiveForm = frmFinder;

        }

        public static void Quit()
        {
            if (!bLoaded) return;

            API.desktop.mnuRun.RemoveItem(mnuSwitchToApp);
            API.desktop.mnuForce.RemoveItem(mnuQuitApp);
            if (API.desktop.mnuForce.ItemCount == 0) API.desktop.mnuForce.Enabled = false;
            if (API.desktop.mnuRun.ItemCount == 0) API.desktop.mnuRun.Enabled = false;

            API.DisplayDesktop();

            frmFinder = null;
            mnuQuitApp = null;
            mnuSwitchToApp = null;
            mnu = null;

            bLoaded = false;
        }

        #endregion

        #region Private Methods

        private static void CreateApp()
        {
            Thread.Sleep(100); // Get the MM time to start
            createForm();
            CreateMenus();
            MM.Stop();
        }

        private static void createForm()
        {
            frmFinder = new Form(API, Colors.LightGray, true, true);

            frmFinder.AddChild(new Label("Drive:", Colors.Black, FontManager.ArialBold, 2, 4, 34, 16));

            cboDrives = new Combobox(42, 2, 86, 20);

            for (int i = 0; i < API.MyDrives.DriveRoots.Length; i++)
            {
                cboDrives.AddItem(API.MyDrives.DriveRoots[i]);
            }
            frmFinder.AddChild(cboDrives);


            fbox = new Filebox(API.MyDrives.RootDirectory, 130, 2, frmFinder.Width - 132, frmFinder.Height - 4);
            frmFinder.AddChild(fbox);

            trvFolders = new Treeview(2, 24, 126, frmFinder.Height - 26);
            LoadTreeview(trvFolders, cboDrives.SelectedItem);
            trvFolders.NodeExpanded += new OnNodeExpanded(trvFolders_NodeExpanded);
            trvFolders.NodeTapped += new OnNodeTap(trvFolders_NodeTapped);
            frmFinder.AddChild(trvFolders);

            cboDrives.SelectedIndexChanged += new OnSelectedIndexChange((object sender, int index) => ChangeDrives());
            fbox.SelectedFileChanged += new OnSelectedFileChanged((object sender, string file) => UpdateMenus());
            fbox.TapHold += new OnTapHold((object sender, point e) => API.ShowContextMenu(CM, e));
            trvFolders.TapHold += new OnTapHold((object sender, point e) => API.ShowContextMenu(CM, e));
        }

        private static void CreateMenus()
        {
            // Application Menus
            mnu = new MenuItem("File Finder");
            mnu.AddItem(new MenuItem("Run"));
            mnu.AddItem(new MenuItem("Send to Desktop"));
            mnu.AddItem(new MenuItem("-"));

            MenuItem mnuActions = new MenuItem("Actions");
            MenuItem mnuANew = new MenuItem("New");
            mnuANew.AddItem(new MenuItem("File"));
            mnuANew.AddItem(new MenuItem("Folder"));
            mnuActions.AddItem(mnuANew);
            mnuActions.AddItem(new MenuItem("-"));

            mnuActions.AddItem(new MenuItem("Copy"));
            mnuActions.AddItem(new MenuItem("Move"));
            mnuActions.AddItem(new MenuItem("Rename"));
            mnuActions.AddItem(new MenuItem("Delete"));
            mnu.AddItem(mnuActions);

            mnu.AddItem(new MenuItem("Refresh"));
            mnu.AddItem(new MenuItem("-"));
            mnu.AddItem(new MenuItem("Quit"));

            mnu.Item(0).tapEvent += new MenuItemTap(MnuOpenRun_tapEvent);           // Open/Run
            mnu.Item(1).tapEvent += new MenuItemTap(MnuLink_tapEvent);              // Send to Desktop

            mnu.Item(3).Item(0).Item(0).tapEvent += new MenuItemTap(MnuNewFile_tapEvent);
            mnu.Item(3).Item(0).Item(1).tapEvent += new MenuItemTap(MnuNewDir_tapEvent);

            mnu.Item(3).Item(2).tapEvent += new MenuItemTap(MnuCopy_tapEvent);
            mnu.Item(3).Item(3).tapEvent += new MenuItemTap(MnuMove_tapEvent);
            mnu.Item(3).Item(4).tapEvent += new MenuItemTap(MnuRename_tapEvent);
            mnu.Item(3).Item(5).tapEvent += new MenuItemTap(MnuDelete_tapEvent);

            mnu.Item(4).tapEvent += new MenuItemTap(MnuRefresh_tapEvent);           // Refresh
            mnu.Item(6).tapEvent += new MenuItemTap((object sender) => Quit());     // Quit

            // Context Menus
            CM = new ContextMenu(new string[] { "Run", "Send to Desktop", "-", "Copy", "Move", "Rename", "Delete", "-", "Refresh" });
            CM.Item(0).tapEvent += new GUI.Controls.MenuItemTap(MnuOpenRun_tapEvent);
            CM.Item(1).tapEvent += new GUI.Controls.MenuItemTap(MnuLink_tapEvent);
            CM.Item(3).tapEvent += new GUI.Controls.MenuItemTap(MnuCopy_tapEvent);
            CM.Item(4).tapEvent += new GUI.Controls.MenuItemTap(MnuMove_tapEvent);
            CM.Item(5).tapEvent += new GUI.Controls.MenuItemTap(MnuRename_tapEvent);
            CM.Item(6).tapEvent += new GUI.Controls.MenuItemTap(MnuDelete_tapEvent);
            CM.Item(8).tapEvent += new GUI.Controls.MenuItemTap(MnuRefresh_tapEvent);

            UpdateMenus();

            // Create Switch/Quit Menus
            mnuSwitchToApp = new MenuItem("File Finder");
            mnuSwitchToApp.tapEvent += new MenuItemTap((object sender) => Show(API));
            mnuSwitchToApp.Tag = "File Finder";
            API.desktop.mnuRun.AddItem(mnuSwitchToApp);
            API.desktop.mnuRun.Enabled = true;

            mnuQuitApp = new MenuItem("File Finder");
            mnuQuitApp.tapEvent += new MenuItemTap((object sender) => Quit());
            mnuQuitApp.Tag = "File Finder";
            API.desktop.mnuForce.AddItem(mnuQuitApp);
            API.desktop.mnuForce.Enabled = true;
        }

        private static string GetDirectoryName(string sPath)
        {
            string sDir = Path.GetDirectoryName(sPath);
            int i = sDir.LastIndexOf("\\");
            return sDir.Substring(i + 1);
        }

        /// <summary>
        /// Loads the Treeview with a list of directories for the current drive
        /// </summary>
        /// <param name="trv">Target Treeview object</param>
        /// <param name="fullpath">Path to load from</param>
        private static void LoadTreeview(Treeview trv, string fullpath)
        {
            trv.Suspended = true;
            trv.ClearNodes();
            TreeviewNode root = new TreeviewNode(fullpath);
            root.Tag = fullpath;
            TreeviewNode nodeToSel = null;

            try
            {
                string[] folders = Directory.GetDirectories(fullpath);
                for (int i = 0; i < folders.Length; i++)
                {
                    TreeviewNode myNode = new TreeviewNode(folders[i].Substring(folders[i].LastIndexOf("\\") + 1));
                    if (Directory.GetDirectories(folders[i]).Length > 0) myNode.AddNode(new TreeviewNode(string.Empty));
                    myNode.Tag = folders[i];
                    if (folders[i] == _selFolder)
                        nodeToSel = myNode;
                    root.AddNode(myNode);

                    // Persistance
                    if (_persistance != null)
                    {
                        for (int j = 0; j < _persistance.Count; j++)
                        {
                            if (_persistance[j] == myNode.Tag)
                            {
                                myNode.Expanded = true;
                                trvFolders_NodeExpanded(trvFolders, myNode);
                                _persistance.RemoveAt(j);
                                break;
                            }
                        }
                    }

                }
            }
            catch (Exception) { }

            if (root.Length > 0) 
                root.Expanded = true;
            trv.AddNode(root);
            if (nodeToSel != null) 
                trv.SelectedNode = nodeToSel;

            _selFolder = string.Empty;
            _persistance = null;
            trv.Suspended = false;
        }

        /// <summary>
        /// Updates menu states based on active selection
        /// </summary>
        private static void UpdateMenus()
        {
            // Application Menus
            if (fbox.SelectedFile == string.Empty)
            {
                mnu.Item(0).Enabled = false;            // Run/Open
                mnu.Item(1).Visible = false;            // Send To Desktop
                mnu.Item(3).Item(2).Enabled = false;    // Copy
                mnu.Item(3).Item(3).Enabled = false;    // Move

                if (trvFolders.SelectedNode == null || trvFolders.SelectedNode == trvFolders.Node(0))
                {
                    mnu.Item(3).Item(4).Enabled = false;    // Rename
                    mnu.Item(3).Item(5).Enabled = false;    // Delete
                }
                else
                {
                    mnu.Item(3).Item(4).Enabled = true;    // Rename
                    mnu.Item(3).Item(5).Enabled = true;    // Delete
                }

                mnu.Item(3).Item(0).Enabled = (trvFolders.SelectedNode == null) ? false : true;    // New

            }
            else
            {
                mnu.Item(0).Text = (Path.GetExtension(fbox.SelectedFile.ToLower()) == ".pxe") ? "Run" : "Open";
                mnu.Item(0).Enabled = true;
                mnu.Item(1).Visible = mnu.Item(0).Enabled;

                mnu.Item(3).Item(0).Enabled = true;     // New
                mnu.Item(3).Item(4).Enabled = true;     // Rename
                mnu.Item(3).Item(5).Enabled = true;     // Delete
                if (fbox.SelectionIsFile)
                {
                    mnu.Item(3).Item(2).Enabled = true;    // Copy
                    mnu.Item(3).Item(3).Enabled = true;    // Move
                }
                else
                {
                    mnu.Item(3).Item(2).Enabled = false;    // Copy
                    mnu.Item(3).Item(3).Enabled = false;    // Move
                }
            }

            // Context Menu
            CM.Item(0).Text = mnu.Item(0).Text;
            CM.Item(0).Enabled = mnu.Item(0).Enabled;       // Open/Run
            CM.Item(1).Visible = mnu.Item(1).Visible;       // Send to Desktop

            CM.Item(3).Enabled = mnu.Item(3).Item(2).Enabled;
            CM.Item(4).Enabled = mnu.Item(3).Item(3).Enabled;
            CM.Item(5).Enabled = mnu.Item(3).Item(4).Enabled;
            CM.Item(6).Enabled = mnu.Item(3).Item(5).Enabled;


        }

        /// <summary>
        /// Keeps track of expanded folder nodes to persist their values
        /// across a structure refresh request
        /// </summary>
        /// <param name="target"></param>
        private static void UpdateNodePersistance(TreeviewNode target)
        {
            TreeviewNode node;

            for (int i = 0; i < target.Length; i++)
            {
                node = target.Node(i);
                if (node.Expanded)
                    _persistance.Add(node.Tag);
                if (node.Length > 0)
                    UpdateNodePersistance(node);
            }
        }

        #endregion

        #region Event Handlers

        private static void ChangeDrives()
        {
            LoadTreeview(trvFolders, cboDrives.SelectedItem);
            fbox.Path = cboDrives.SelectedItem;
        }

        /// <summary>
        /// Copies the selected file (folders not allowed)
        /// </summary>
        /// <param name="sender"></param>
        private static void MnuCopy_tapEvent(object sender)
        {
            string sRes = API.SaveFile("Copy " + Path.GetFileName(fbox.SelectedFile), true, Path.GetDirectoryName(fbox.SelectedFile));
            if (sRes == string.Empty)
                return;
            File.Copy(fbox.SelectedFile, sRes);
            _selFile = sRes;
            MnuRefresh_tapEvent(sender);
        }

        /// <summary>
        /// Deletes the selected file/folder
        /// </summary>
        /// <param name="sender"></param>
        private static void MnuDelete_tapEvent(object sender)
        {
            bool isFile = fbox.SelectionIsFile;
            string sPath = fbox.SelectedFile;

            if (sPath == string.Empty)
            {
                sPath = FileManager.NormalizeDirectory((string)trvFolders.SelectedNode.Tag);
                isFile = false;
            }

            if (isFile)
            {
                if (API.Prompt("Are you sure you wish to delete '" + Path.GetFileName(sPath) + "'?", "File Finder", PromptType.YesNo) == PromptResult.No)
                    return;
                File.Delete(sPath);
            }
            else
            {
                if (API.Prompt("Are you sure you wish to delete '" + GetDirectoryName(sPath) + "' and all its contents?", "File Finder", PromptType.YesNo) == PromptResult.No)
                    return; 
                Directory.Delete(sPath, true);
            }

            MnuRefresh_tapEvent(sender);
        }

        /// <summary>
        /// Creates a desktop link for the selected file
        /// </summary>
        /// <param name="sender"></param>
        private static void MnuLink_tapEvent(object sender)
        {
            string sPath = fbox.SelectedFile;

            try
            {
                byte[] b = API.CreateLink(sPath);
                string file = FileManager.NormalizeDirectory(API.MyDrives.RootDirectory) + "pyxis\\desktop\\" + PyxisAPI.StringReplace(Path.GetFileName(sPath), Path.GetExtension(sPath), "");
                if (File.Exists(file + ".lnk"))
                    file = FileManager.GetFile(file);

                FileStream fs = new FileStream(file + ".lnk", FileMode.CreateNew, FileAccess.Write);
                fs.Write(b, 0, b.Length);
                fs.Close();

                API.Prompt(Path.GetFileName(sPath) + " has been sent to the desktop.", "File Finder", PromptType.OKOnly);
                API.MyDrives.FlushFileSystems();
            }
            catch (Exception)
            {
                API.Prompt("Could not create link.\n" + sPath, "File Finder", PromptType.OKOnly);
            }
        }

        /// <summary>
        /// Moves the selected file (folders not allowed)
        /// </summary>
        /// <param name="sender"></param>
        private static void MnuMove_tapEvent(object sender)
        {
            string sRes = API.SaveFile("Move " + Path.GetFileName(fbox.SelectedFile), true, Path.GetDirectoryName(fbox.SelectedFile));
            if (sRes == string.Empty)
                return;
            File.Move(fbox.SelectedFile, sRes);
            _selFile = sRes;
            MnuRefresh_tapEvent(sender);
        }

        /// <summary>
        /// Creates a new directory under the current path
        /// </summary>
        /// <param name="sender"></param>
        private static void MnuNewDir_tapEvent(object sender)
        {
            string name = API.Inputbox("Please enter a name for your new directory.", "Create Directory");
            if (name == "") return;

            try
            {
                if (name.IndexOf("\\") < 0)
                {
                    Directory.CreateDirectory(FileManager.NormalizeDirectory(fbox.Path) + name);
                }
                else
                {
                    Directory.CreateDirectory(name);
                }
                API.MyDrives.FlushFileSystems();
                MnuRefresh_tapEvent(sender);
            }
            catch (Exception e)
            {
                API.Prompt("Could not create directory '" + name + "'!\n" + e.Message, "File Finder", PromptType.OKOnly);
            }
        }

        /// <summary>
        /// Creates a new file
        /// </summary>
        /// <param name="sender"></param>
        private static void MnuNewFile_tapEvent(object sender)
        {
            string name = API.Inputbox("Please enter a name for your new file.", "Create File");
            if (name == "") return;

            try
            {
                if (name.IndexOf("\\") < 0)
                {
                    File.Create(FileManager.NormalizeDirectory(fbox.Path) + name);
                }
                else
                {
                    File.Create( name);
                }
                API.MyDrives.FlushFileSystems();
                MnuRefresh_tapEvent(sender);
            }
            catch (Exception e)
            {
                API.Prompt("Could not create file '" + name + "'!\n" + e.Message, "File Finder", PromptType.OKOnly);
            }
        }

        /// <summary>
        /// Handles Open/Run requests
        /// </summary>
        /// <param name="sender"></param>
        private static void MnuOpenRun_tapEvent(object sender)
        {
            string sPath = fbox.SelectedFile;

            if (mnu.Item(0).Text == "Run")
            {
                // Launch Application
                API.StartApplication(sPath);
            }
            else
            {
                if (File.Exists(sPath))
                {
                    // Open file
                    string sFile = API.MyFiles.GetAssociatedApplication(Path.GetExtension(sPath));
                    if (sFile == string.Empty)
                    {
                        API.Prompt("No associated application.", "File Finder", PromptType.OKOnly);
                        return;
                    }
                    sFile = PyxisAPI.StringReplace(sFile, "%root", API.MyDrives.RootDirectory);
                    sFile = PyxisAPI.StringReplace(sFile, "%1", sPath);

                    string sApp;
                    string[] sParam = null;

                    if (sFile.IndexOf(' ') > 0)
                    {
                        sApp = sFile.Substring(0, sFile.IndexOf(' '));
                        sParam = sFile.Substring(sFile.IndexOf(' ') + 1).Split(',');
                    }
                    else
                        sApp = sFile;

                    switch (sApp)
                    {
                        case "-1":
                            PictureViewer.Show(API, sParam);
                            break;
                        case "-2":
                            API.InstallApplication(sParam[0]);
                            break;
                        default:
                            API.StartApplication(sApp, sParam);
                            break;
                    }
                } else if (Directory.Exists(sPath))
                {
                    fbox.Path = sPath;
                }
            }
        }

        /// <summary>
        /// Refreshes the file/folder structure
        /// </summary>
        /// <param name="sender"></param>
        private static void MnuRefresh_tapEvent(object sender)
        {
            // Persistance
            _persistance = new ArrayList();
            UpdateNodePersistance(trvFolders.Node(0));
            if (trvFolders.SelectedNode != null)
                _selFolder = (string)trvFolders.SelectedNode.Tag;
            string selFile = (_selFile == string.Empty) ? fbox.SelectedFile : _selFile;

            // Reload Treeview
            LoadTreeview(trvFolders, cboDrives.SelectedItem);

            // Refresh filebox
            fbox.Refresh();
            fbox.SelectedFile = selFile;
            _selFile = string.Empty;
        }

        /// <summary>
        /// Renames the selected file/folder
        /// </summary>
        /// <param name="sender"></param>
        private static void MnuRename_tapEvent(object sender)
        {
            bool isFile = fbox.SelectionIsFile;
            string sPath = fbox.SelectedFile;
            string sRes;

            if (sPath == string.Empty)
            {
                sPath = FileManager.NormalizeDirectory((string)trvFolders.SelectedNode.Tag);
                isFile = false;
            }

            if (isFile)
            {
                sRes = API.Inputbox("Enter a new name for your file:", "Rename File", Path.GetFileName(sPath));
                if (sRes == string.Empty || sRes == Path.GetFileName(sPath))
                    return;

                _selFile = PyxisAPI.StringReplace(sPath, Path.GetFileName(sPath), sRes);
                File.Move(sPath, _selFile);
            }
            else
            {
                sRes = API.Inputbox("Enter a new name for your folder:", "Rename Folder", GetDirectoryName(sPath));
                if (sRes == string.Empty || sRes == GetDirectoryName(sPath))
                    return;

                Directory.Move(sPath, PyxisAPI.StringReplace(sPath, GetDirectoryName(sPath), sRes));
            }

            MnuRefresh_tapEvent(sender);        
        }

        /// <summary>
        /// Checks for depth loading on node expansion
        /// </summary>
        /// <param name="sender">Node parent (aka trewview)</param>
        /// <param name="node">Node being expanded</param>
        private static void trvFolders_NodeExpanded(object sender, TreeviewNode node)
        {
            if (node.Length == 1 && node.Node(0).Text == string.Empty)
            {
                Treeview tv = (Treeview)sender;
                TreeviewNode nodeToSel = null;
                string[] folders;

                tv.Suspended = true;
                node.ClearNodes();

                folders = Directory.GetDirectories((string)node.Tag);
                for (int i = 0; i < folders.Length; i++)
                {
                    TreeviewNode myNode = new TreeviewNode(folders[i].Substring(folders[i].LastIndexOf("\\") + 1));
                    if (Directory.GetDirectories(folders[i]).Length > 0) myNode.AddNode(new TreeviewNode(string.Empty));
                    myNode.Tag = folders[i];
                    node.AddNode(myNode);
                    if (folders[i] == _selFolder)
                        nodeToSel = myNode;
                }
                node.Expanded = true;
                if (nodeToSel != null) tv.SelectedNode = nodeToSel;
                tv.Suspended = false;
            }
        }

        /// <summary>
        /// Handles changing of selected tree node
        /// </summary>
        /// <param name="node"></param>
        /// <param name="e"></param>
        private static void trvFolders_NodeTapped(TreeviewNode node, point e)
        {
            fbox.Path = (string)node.Tag;
            UpdateMenus();
        }

        #endregion

    }
}
